<!DOCTYPE html>
<html lang=zh-CN>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="google-site-verification" content="xBT4GhYoi5qRD5tr338pgPM5OWHHIDR6mNg1a3euekI" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta property="og:description" content="samuel-24276的博客">
    <meta property="og:type" content="website">
    <meta name="description" content="samuel-24276的博客">
    <meta name="keyword"  content="aircloud">
    <link rel="shortcut icon" href="/myblog/img/favicon.ico">

    <title>
        
        Effective C++读书笔记（1） - samuel-24276的博客 | samuel-24276&#39;s Blog
        
    </title>

    <!-- Custom CSS -->
    
<link rel="stylesheet" href="/myblog/css/aircloud.css">

    
<link rel="stylesheet" href="/myblog/css/gitment.css">

    <!--<link rel="stylesheet" href="https://imsun.github.io/gitment/style/default.css">-->
    <link href="//at.alicdn.com/t/font_620856_28hi1hpxx24.css" rel="stylesheet" type="text/css">
    <!-- ga & ba script hoook -->
    <script></script>
<meta name="generator" content="Hexo 7.1.1"></head>

<body>

<div class="site-nav-toggle" id="site-nav-toggle">
    <button>
        <span class="btn-bar"></span>
        <span class="btn-bar"></span>
        <span class="btn-bar"></span>
    </button>
</div>

<div class="index-about">
    <i> Like what you do, do what you like </i>
</div>

<div class="index-container">
    
    <div class="index-left">
        
<div class="nav" id="nav">
    <div class="avatar-name">
        <div class="avatar ">
            <img src="/myblog/img/avatar.jpg" />
        </div>
        <div class="name">
            <i>samuel-24276</i>
        </div>
    </div>
    <div class="contents" id="nav-content">
        <ul>
            <li >
                <a href="/myblog/">
                    <i class="iconfont icon-shouye1"></i>
                    <span>主页</span>
                </a>
            </li>
            <li >
                <a href="/myblog/tags">
                    <i class="iconfont icon-biaoqian1"></i>
                    <span>标签</span>
                </a>
            </li>
            <li >
                <a href="/myblog/archives">
                    <i class="iconfont icon-guidang2"></i>
                    <span>存档</span>
                </a>
            </li>
            <li >
                <a href="/myblog/collect/">
                    <i class="iconfont icon-shoucang1"></i>
                    <span>收藏</span>
                </a>
            </li>
            <li >
                <a href="/myblog/about/">
                    <i class="iconfont icon-guanyu2"></i>
                    <span>关于</span>
                </a>
            </li>
            
            <li>
                <a id="search">
                    <i class="iconfont icon-sousuo1"></i>
                    <span>搜索</span>
                </a>
            </li>
            
        </ul>
    </div>
    
        <div id="toc" class="toc-article">
    <ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#1-%E8%AE%A9%E8%87%AA%E5%B7%B1%E4%B9%A0%E6%83%AFC"><span class="toc-text">1.让自己习惯C++</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9C%AF%E8%AF%AD"><span class="toc-text">术语</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9D%A1%E6%AC%BE01%EF%BC%9A%E8%A7%86C-%E4%B8%BA%E4%B8%80%E4%B8%AA%E8%AF%AD%E8%A8%80%E8%81%94%E9%82%A6"><span class="toc-text">条款01：视C++为一个语言联邦</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9D%A1%E6%AC%BE02%EF%BC%9A%E5%B0%BD%E9%87%8F%E4%BB%A5const-enum-inline%E6%9B%BF%E6%8D%A2-define"><span class="toc-text">条款02：尽量以const,enum,inline替换#define</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9D%A1%E6%AC%BE03%EF%BC%9A%E5%B0%BD%E5%8F%AF%E8%83%BD%E4%BD%BF%E7%94%A8const"><span class="toc-text">条款03：尽可能使用const</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%8C%87%E9%92%88%E6%88%96%E8%BF%AD%E4%BB%A3%E5%99%A8"><span class="toc-text">指针或迭代器</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%87%BD%E6%95%B0%E8%BF%94%E5%9B%9E%E5%80%BC"><span class="toc-text">函数返回值</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#const%E6%88%90%E5%91%98%E5%87%BD%E6%95%B0"><span class="toc-text">const成员函数</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%9C%A8const%E5%92%8Cnon-const%E6%88%90%E5%91%98%E5%87%BD%E6%95%B0%E4%B8%AD%E9%81%BF%E5%85%8D%E9%87%8D%E5%A4%8D"><span class="toc-text">在const和non-const成员函数中避免重复</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%9D%A1%E6%AC%BE04%EF%BC%9A%E7%A1%AE%E5%AE%9A%E5%AF%B9%E8%B1%A1%E8%A2%AB%E4%BD%BF%E7%94%A8%E5%89%8D%E5%B7%B2%E5%85%88%E8%A2%AB%E5%88%9D%E5%A7%8B%E5%8C%96"><span class="toc-text">条款04：确定对象被使用前已先被初始化</span></a></li></ol></li></ol>
</div>
    
</div>


<div class="search-field" id="search-field">
    <div class="search-bg" id="search-bg"></div>
    <div class="search-container">
        <div class="search-input">
            <span id="esc-search"> <i class="icon-fanhui iconfont"></i></span>
            <input id="search-input"/>
            <span id="begin-search">搜索</span>
        </div>
        <div class="search-result-container" id="search-result-container">

        </div>
    </div>
</div>

        <div class="index-about-mobile">
            <i> Like what you do, do what you like </i>
        </div>
    </div>
    
    <div class="index-middle">
        <!-- Main Content -->
        


<div class="post-container">
    <div class="post-title">
        Effective C++读书笔记（1）
    </div>

    <div class="post-meta">
        <span class="attr">发布于：<span>2022-06-25 09:24:21</span></span>
        
        <span class="attr">标签：/
        
        <a class="tag" href="/myblog/tags/#C++" title="C++">C++</a>
        <span>/</span>
        
        
        </span>
        <span class="attr">访问：<span id="busuanzi_value_page_pv"></span>
</span>
</span>
    </div>
    <div class="post-content no-indent">
        <h1 id="1-让自己习惯C"><a href="#1-让自己习惯C" class="headerlink" title="1.让自己习惯C++"></a>1.让自己习惯C++</h1><h2 id="术语"><a href="#术语" class="headerlink" title="术语"></a>术语</h2><p><strong>声明（declaration）</strong>是告诉编译器某个东西的名称和类型（type），但略去细节。下面都是声明：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">extern</span> <span class="type">int</span> x;						<span class="comment">// 对象声明</span></span><br><span class="line"><span class="function">std::<span class="type">size_t</span> <span class="title">numDigits</span><span class="params">(<span class="type">int</span> number)</span></span>;	<span class="comment">// 函数声明</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Widget</span>;						<span class="comment">// 类声明</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;				<span class="comment">// 模板声明</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">GraphNode</span>；</span><br></pre></td></tr></table></figure>

<p>每个函数的声明揭示其<strong>函数签名（signature）</strong>，也就是参数和返回类型。一个函数的签名等同于该函数的类型。</p>
<p><strong>定义（definition）</strong>的任务是提供编译器一些声明所遗漏的细节。对对象而言，定义是编译器为此对象拨发内存的地点。对函数或函数模板而言，定义提供了代码本体。对类或类模板而言，定义列出他们的成员。</p>
<p><strong>初始化（initialization）</strong>是“给予对象初值”的过程。显式（explicit）构造函数禁止编译器执行非预期（往往也不被期望）的类型转换，推荐使用。</p>
<p>copy构造函数被用来“以同型对象初始化自我对象”，有浅拷贝和深拷贝两种方式，默认是浅拷贝，当copy对象内有申请内存时，必须进行深拷贝，重写拷贝构造函数。因为浅拷贝只是拷贝内存地址，析构时该内存指向地址被析构两次；且可能发生不希望的值改变。</p>
<h2 id="条款01：视C-为一个语言联邦"><a href="#条款01：视C-为一个语言联邦" class="headerlink" title="条款01：视C++为一个语言联邦"></a>条款01：视C++为一个语言联邦</h2><blockquote>
<p>View C++ as a federation of languages.</p>
</blockquote>
<p>今天的C++已经是一个支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式的多重范型编程语言。主要次语言有四个：</p>
<ul>
<li>C：面向过程，在高效编程中有较多缺陷：没有模板、没有异常、没有重载…</li>
<li>Object-Oriented C++：面向对象。</li>
<li>Template C++：泛型编程，带来了新的编程范型，也就是模板元编程（TMP）。</li>
<li>STL：C++标准库。</li>
</ul>
<p>函数传参时值传递更高效的是C、STL的迭代器和函数对象，引用传递更高效的是面向对象和泛型编程的C++。</p>
<blockquote>
<p>C++高效编程守则视情况而定，取决于你使用C++的哪一部分。</p>
</blockquote>
<h2 id="条款02：尽量以const-enum-inline替换-define"><a href="#条款02：尽量以const-enum-inline替换-define" class="headerlink" title="条款02：尽量以const,enum,inline替换#define"></a>条款02：尽量以const,enum,inline替换#define</h2><blockquote>
<p>Prefer consts,enums,and inlines to #defines.</p>
</blockquote>
<p>更直白的解释是：<strong>宁可以编译器替换预处理器</strong>。当你做出如下事情：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> PI 3.14</span></span><br></pre></td></tr></table></figure>

<p>记号名称PI也许从未被编译器看见；也许在编译器开始处理源码之前它就被预处理器移走了。于是记号名称PI可能没进入<strong>记号表（symbol table）</strong>内。当你使用这个常量但获得一个编译错误时，错误信息可能提到3.14而不是PI，于是你将因为追踪PI而浪费时间。</p>
<p>当我们以常量替换#define，有两种情况值得一提。</p>
<ol>
<li>定义常量指针：由于常量定义式通常被放在头文件内（以便被不同源码包含），因此有必要将指针声明为const，关于const，条款3有完整讨论。</li>
<li>class专属常量：为了将常量作用域限制于class内它必须成为成员变量；为确保此常量至多只有一份实体，它必须成为静态（static）成员变量。</li>
</ol>
<p>如果类专属常量为整数类型，需要特殊处理。只要不取它们的地址，可以声明并使用它们而无需提供定义式。但如果你取某个class专属常量的地址，或你的编译器坚持看到一个定义式，你就必须另外提供定义式如下：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> ClassName::变量名;<span class="comment">// 由于声明时已经获得初值，此时定义不再设初值。</span></span><br></pre></td></tr></table></figure>

<p>若旧式编译器不支持static成员在声明时赋初值，可以将初值放在定义式：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">const</span> <span class="type">int</span> ClassName::变量名 = 初值;</span><br></pre></td></tr></table></figure>

<p>这几乎是你在任何时候唯一需要做的事情。唯一例外是当你在class编译期间需要一个class常量值（如数组大小），万一编译器不允许static整数常量直接初始化，可改用所谓的“the enum hack”补偿做法。即：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> 类名</span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">enum</span> &#123; 变量名 = 初值 &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>如果你不想让别人获得一个pointer或reference指向你的某个整数常量，enum可帮助你实现这个约束。</p>
<blockquote>
<ul>
<li>对于单纯常量，最好以const对象或enums替换#define</li>
<li>对于形似函数的宏（macros），最好改用inline函数替换#define</li>
</ul>
</blockquote>
<h2 id="条款03：尽可能使用const"><a href="#条款03：尽可能使用const" class="headerlink" title="条款03：尽可能使用const"></a>条款03：尽可能使用const</h2><blockquote>
<p>Use const whenever possible.</p>
</blockquote>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">char</span> greeting[]=<span class="string">&quot;hello&quot;</span>;</span><br><span class="line"><span class="type">char</span> *p = greeting;</span><br><span class="line"><span class="type">const</span> <span class="type">char</span> *p = greeting;<span class="comment">// 常量指针，指针指向的内存是不可变化的，被指物是常量</span></span><br><span class="line"><span class="type">char</span> * <span class="type">const</span> p = greeting;<span class="comment">// 指针常量，指针指向是不可变化的，自身是常量</span></span><br><span class="line"><span class="type">const</span> <span class="type">char</span> * <span class="type">const</span> p = greeting;<span class="comment">// 常量指针常量，指针指向不可变，指向的内存不可变</span></span><br></pre></td></tr></table></figure>

<p><strong>const出现在星号左边，表示被指物是常量；出现在星号右边，表示指针自身是常量</strong>。</p>
<h3 id="指针或迭代器"><a href="#指针或迭代器" class="headerlink" title="指针或迭代器"></a>指针或迭代器</h3><p>STL迭代器以指针为根据塑模出来，作用相当于T*指针。</p>
<p>声明const iterator相当于T* const，要想表示const T*可以使用const_iterator。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">std::vector&lt;<span class="type">int</span>&gt; vec_int;</span><br><span class="line"><span class="type">const</span> std::vector&lt;<span class="type">int</span>&gt;::iterator iter = vec_int.<span class="built_in">begin</span>();<span class="comment">// iter = T* const</span></span><br><span class="line">*iter = <span class="number">10</span>;</span><br><span class="line">++iter;<span class="comment">// error,iter是指针常量</span></span><br><span class="line">std::vector&lt;<span class="type">int</span>&gt;::const_iterator cIter = vec_int.<span class="built_in">begin</span>();<span class="comment">//cIter = const T*</span></span><br><span class="line">*cIter = <span class="number">10</span>;<span class="comment">// error，cIter是常量指针</span></span><br><span class="line">++cIter;</span><br></pre></td></tr></table></figure>

<h3 id="函数返回值"><a href="#函数返回值" class="headerlink" title="函数返回值"></a>函数返回值</h3><p>令函数返回一个常量值，可以降低因客户错误而造成的意外，又不至于放弃安全性和高效性。考虑有理数的operator*声明：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Rational</span> &#123;...&#125;;</span><br><span class="line"><span class="type">const</span> Rational <span class="keyword">operator</span>*(<span class="type">const</span> Rational&amp; lhs, <span class="type">const</span> Rational&amp; rhs);</span><br><span class="line"></span><br><span class="line">Rational a,b,c;</span><br><span class="line">...</span><br><span class="line">(a*b) = c;</span><br><span class="line"><span class="keyword">if</span>(a*b = c) ...<span class="comment">// 因打字错误，若非const编译时不会报错</span></span><br></pre></td></tr></table></figure>

<h3 id="const成员函数"><a href="#const成员函数" class="headerlink" title="const成员函数"></a>const成员函数</h3><p><strong>将const实施于成员函数的目的，是为了确认该成员函数可作用于const对象身上（表明该成员函数不会变动成员变量）</strong>。</p>
<ol>
<li>得知哪个函数可以改动对象内容而哪个函数不行，很是重要。</li>
<li>使“操作const对象”成为可能。</li>
</ol>
<p>两个成员函数如果只是常量性不同，可以被重载。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">TextBlock</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span>&amp; <span class="keyword">operator</span>[](std::<span class="type">size_t</span> pos) <span class="type">const</span></span><br><span class="line">    &#123; <span class="keyword">return</span> text[pos]; &#125;</span><br><span class="line">    <span class="type">char</span>&amp; <span class="keyword">operator</span>[](std::<span class="type">size_t</span> pos)</span><br><span class="line">    &#123; <span class="keyword">return</span> text[pos]; &#125;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    std::string text;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>TextBlock的operator[]可被这么使用：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">TextBlock <span class="title">tb</span><span class="params">(<span class="string">&quot;hello&quot;</span>)</span></span>;</span><br><span class="line">std::cout&lt;&lt;tb[<span class="number">0</span>];</span><br><span class="line"><span class="function"><span class="type">const</span> TextBlock <span class="title">ctb</span><span class="params">(<span class="string">&quot;hello&quot;</span>)</span></span>;</span><br><span class="line">std::cout&lt;&lt;ctb[<span class="number">0</span>];</span><br><span class="line">tb[<span class="number">0</span>] = <span class="string">&#x27;H&#x27;</span>;</span><br><span class="line">ctb[<span class="number">0</span>] = <span class="string">&#x27;H&#x27;</span>;<span class="comment">// error，const对象</span></span><br></pre></td></tr></table></figure>

<p>non-const的operator[]返回类型是一个char引用，若是一个char，则无法赋值，那是因为，如果函数返回类型是内置类型，那么改动函数返回值从来不合法。即使合法，改动的也只是副本，不是你想要的行为。</p>
<p>成员函数如果是const意味着什么？这有两个流行概念：<strong>bitwise constness</strong>(又称physical constness)和<strong>logical constness</strong>。</p>
<p><strong>bitwise constness</strong>是C++对常量性的定义，因此const成员函数不可以更改对象内任何non-static成员变量。</p>
<p>假设我们有一个CTextBlock，它将数据存储为char*而不是string，因为它需要和一个不认识string对象的C API沟通：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">CTextBlock</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line">    <span class="type">char</span>&amp; <span class="keyword">operator</span>[](std::<span class="type">size_t</span> pos) <span class="type">const</span></span><br><span class="line">    &#123; <span class="keyword">return</span> pText[pos]; &#125;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="type">char</span>* pText;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>请注意，operator[]实现代码并不更改pText，它是bitwise const，让我们看下它允许发生什么事：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">const</span> CTextBlock <span class="title">cctb</span><span class="params">(<span class="string">&quot;Hello&quot;</span>)</span></span>;</span><br><span class="line"><span class="type">char</span>* pc = &amp;cctb[<span class="number">0</span>];</span><br><span class="line">pc = <span class="string">&#x27;J&#x27;</span>;<span class="comment">// cctb的pText = &quot;Jello&quot;</span></span><br></pre></td></tr></table></figure>

<p>这其中当然不该有任何错误：你创建一个常量对象并初始化，对它调用const成员函数，但你最终还是改变了它的值。</p>
<p>这种情况下导出所谓的logical constness。这一派拥护者认为，一个const成员函数可以修改它所处理的对象内的某些bits，但只有在客户端侦测不出的情况下才得如此。例如你的CTextBlock有可能高速缓存文本区块的长度以便应付询问：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">CTextBlock</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line">    <span class="function">std::<span class="type">size_t</span> <span class="title">length</span><span class="params">()</span> <span class="type">const</span></span>;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="type">char</span>* pText;</span><br><span class="line">    std::<span class="type">size_t</span> textLength;<span class="comment">// 最近一次计算的文本区块长度</span></span><br><span class="line">    <span class="type">bool</span> lengthIsValid;<span class="comment">// 目前的长度是否有效</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function">std::<span class="type">size_t</span> <span class="title">CTextBlock::length</span><span class="params">()</span> <span class="type">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(!lengthIsValid)</span><br><span class="line">    &#123;</span><br><span class="line">        textLength = std::<span class="built_in">strlen</span>(pText);<span class="comment">// error,在const成员函数内不能赋值给textLength和lengthIsValid</span></span><br><span class="line">        lengthIsValid = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> textLength;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>length()的实现当然不是bitwise const，因为成员变量textLength和lengthIsValid都可能被修改。这两个数据修改对const CTextBlock对象而言虽然可接受，但编译器不同意，怎么办？</p>
<p>利用C++的一个与const相关的摆动场：<strong>mutable（可变的）</strong>。mutable释放掉non-static成员变量的bitwise constness约束：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">CTextBlock</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line">    <span class="function">std::<span class="type">size_t</span> <span class="title">length</span><span class="params">()</span> <span class="type">const</span></span>;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="type">char</span>* pText;</span><br><span class="line">    <span class="keyword">mutable</span> std::<span class="type">size_t</span> textLength;</span><br><span class="line">    <span class="keyword">mutable</span> <span class="type">bool</span> lengthIsValid;<span class="comment">// 这些mutable修饰的成员变量可能总被更改，即使在const成员函数内</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function">std::<span class="type">size_t</span> <span class="title">CTextBlock::length</span><span class="params">()</span> <span class="type">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span>(!lengthIsValid)</span><br><span class="line">    &#123;</span><br><span class="line">        textLength = std::<span class="built_in">strlen</span>(pText);<span class="comment">// error,在const成员函数内不能赋值给textLength和lengthIsValid</span></span><br><span class="line">        lengthIsValid = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> textLength;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="在const和non-const成员函数中避免重复"><a href="#在const和non-const成员函数中避免重复" class="headerlink" title="在const和non-const成员函数中避免重复"></a>在const和non-const成员函数中避免重复</h3><p>对于”bitwise-constness非我所欲“的问题，mutable是个解决办法，但它不能解决所有的const相关难题。</p>
<p>假设TextBlock的operator[]访问字符时，也要执行边界检查，日志信息记录，数据完整性检验，这些通用操作同时放进const和non-const的operator[]中，会导致代码重复。</p>
<p>你真正该做的是<strong>实现operator[]的机能一次并使用它两次</strong>。也就是说，你必须令其中一个调用另一个。这促使我们将常量性移除。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">TextBlock</span>&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line">    <span class="type">const</span> <span class="type">char</span>&amp; <span class="keyword">operator</span>[](std::<span class="type">size_t</span> pos) <span class="type">const</span></span><br><span class="line">    &#123;</span><br><span class="line">        ...</span><br><span class="line">		...</span><br><span class="line">		...</span><br><span class="line">        <span class="keyword">return</span> pText[pos];</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">char</span>&amp; <span class="keyword">operator</span>[](std::<span class="type">size_t</span> pos) <span class="type">const</span><span class="comment">// 现在只调用const operator[]</span></span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">const_cast</span>&lt;<span class="type">char</span>&amp;&gt;(					<span class="comment">// 将op[]返回值的const移除</span></span><br><span class="line">        	<span class="built_in">static_cast</span>&lt;<span class="type">const</span> TextBlock&amp;&gt;(*<span class="keyword">this</span>)	<span class="comment">// 为*this加上const</span></span><br><span class="line">            [pos]									<span class="comment">// 调用const op[]</span></span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="type">char</span>* pText;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>



<blockquote>
<ol>
<li>将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。</li>
<li>编译器强制实行bitwise constness，但你编写程序时应使用“概念上的常量性”（conceptual constness）。</li>
<li>当const和non-const成员函数有着实质等价的实现时，令non-const版本调用const版本可避免代码重复（反之不可以，违背了const的定义）。</li>
</ol>
</blockquote>
<h2 id="条款04：确定对象被使用前已先被初始化"><a href="#条款04：确定对象被使用前已先被初始化" class="headerlink" title="条款04：确定对象被使用前已先被初始化"></a>条款04：确定对象被使用前已先被初始化</h2><blockquote>
<p>Make sure that objects are initialized before they’re used.</p>
</blockquote>
<p>如果你使用C part of C++而且初始化可能招致运行期成本，那么就不保证发生初始化。一旦进入non-C parts of C++，规则有些变化。这就很好地解释了为什么array（来自C part of C++）不保证其内容被初始化，而vector（来自STL part of C++）却有此保证。</p>
<p><strong>永远在使用对象之前先将它初始化</strong>。对于无任何成员的内置类型，你必须手工完成此事。至于内置类型以外的任何其他东西，初始化责任落在构造函数身上。规则很简单：确保每一个构造函数都将对象的每一个成员初始化。关键是别混淆赋值（assignment）和初始化（initialization）。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Book</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Book</span>(std::string name, std::string company, std::string author, <span class="type">long</span> <span class="type">long</span> id);</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    std::string name;</span><br><span class="line">    std::string company;</span><br><span class="line">    std::string author;</span><br><span class="line">    <span class="type">long</span> <span class="type">long</span> id;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">Book::<span class="built_in">Book</span>(std::string name, std::string company, std::string author, <span class="type">long</span> <span class="type">long</span> id)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">// 这些都是赋值而非初始化</span></span><br><span class="line">    <span class="keyword">this</span>-&gt;name = name;</span><br><span class="line">    <span class="keyword">this</span>-&gt;company = company;</span><br><span class="line">    <span class="keyword">this</span>-&gt;author = author;</span><br><span class="line">    <span class="keyword">this</span>-&gt;id = id;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>C++规定，对象的成员变量初始化动作发生在进入构造函数本体之前。构造函数的较好写法是，使用所谓的成员初值列（初始化列表）替换赋值动作（效率较高                                                                ）：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Book::<span class="built_in">Book</span>(std::string name, std::string company, std::string author, <span class="type">long</span> <span class="type">long</span> id)</span><br><span class="line">    : <span class="built_in">name</span>(name)</span><br><span class="line">    , <span class="built_in">company</span>(company)</span><br><span class="line">    , <span class="built_in">author</span>(author)</span><br><span class="line">    , <span class="built_in">id</span>(id)</span><br><span class="line">&#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>对大多数类型而言，构造函数内赋值先调用default构造函数初始化，再在构造函数内赋新值；成员初值列只调用一次成员变量的copy构造函数相对高效，对于复杂类型会高效得多。</p>
<p>有些情况下即使成员变量属于内置类型（那么其初始化与赋值成本相同），也一定要使用初值列。是的，如果成员变量是const或references，它们就一定需要初值，不能被赋值（见条款5）.最简单的方法是：<strong>永远使用成员初值列</strong>。</p>
<p><strong>C++中class的成员变量总是以其声明次序被初始化，即使它们在成员初值列中以不同的次序出现</strong>。</p>
<p>所谓static对象，其寿命从被构造出来直到程序结束为止。这种对象包括global对象、定义于namespace作用域内的对象、在class内、在函数内、以及在file作用域内被声明为static的对象。函数内的static对象被称为 <strong>local static 对象</strong>（因为它们对函数而言是local），其他的static 对象是 non-local static 对象。</p>
<p>所谓<strong>编译单元</strong>（translation unit）是指产出单一目标文件的那些源码。基本上它是单一源码文件加上其所包含的头文件。</p>
<p>现在，我们聊一下<strong>不同编译单元内定义的 non-local static 对象的初始化顺序</strong>。至少有两个源码文件，每一个内含至少一个 non-local static 对象（也就是说该对象是global或者位于namespace作用域内，抑或在class或file作用域内被声明为static）。问题出现了：<em>如果某编译单元内的某个 non-local static 对象的初始化使用了另一编译单元内的某个 non-local static 对象，它所用的这个对象可能尚未初始化，因为C++对“定义于不同编译单元内的 non-local static 对象”的初始化顺序并无明确定义</em>。</p>
<p>实例可以帮助理解。假设你有一个FIleSystem class，它让互联网上的文件看起来好像位于本机。由于这个class使世界看起来像一个单一文件系统，你可能会产出一个特殊对象，位于global或namespace作用域内，象征单一文件系统：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">FileSystem</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line">    <span class="function">std::<span class="type">size_t</span> <span class="title">numDisks</span><span class="params">()</span> <span class="type">const</span></span>;<span class="comment">// 众多成员函数之一</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">extern</span> FileSystem tfs;<span class="comment">// 预备给客户使用的对象，“the file system”</span></span><br></pre></td></tr></table></figure>

<p>现在假设某些客户建立了一个class用以处理文件系统内的目录，很自然他们的class会用上FileSystem对象：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Directory</span><span class="comment">// 由程序库客户建立</span></span><br><span class="line">&#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="built_in">Directory</span>( params );</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br><span class="line">Directory::<span class="built_in">Directory</span>( params )</span><br><span class="line">&#123;</span><br><span class="line">	...</span><br><span class="line">    std::<span class="type">size_t</span> disks = tfs.<span class="built_in">numDisks</span>();<span class="comment">// 使用tfs对象</span></span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>进一步假设，这些客户决定创建一个Directory对象，用来放置临时文件：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">Directory <span class="title">temDir</span><span class="params">( params )</span></span>;<span class="comment">// 为临时文件而做出的目录</span></span><br></pre></td></tr></table></figure>

<p>现在，初始化次序的重要性凸显出来了：除非tfs在tempDir之前被初始化，否则tempDir的构造函数会使用尚未初始化的tfs。但tfs和tempDir是定义于不同编译单元内的 non-local static 对象，我们无法确定初始化顺序。</p>
<p>幸运的是一个小小的设计便可以完全消除这个问题。唯一需要做的是：<strong>将每个 non-local static 对象搬到自己的专属函数内（该对象在此函数内被声明为static，仿真函数）</strong>。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数，而不直接指涉这些对象。换句话说，<em>non-local static 对象被 local static 对象替换了</em>。</p>
<p>这个手法的基础在于：C++保证，函数内的 local static 对象会在“该函数被调用期间”“首次遇上该对象之定义”时被初始化。刚才设计的方法保证了你获得的那个reference将指向一个历经初始化的对象。更棒的是，如果你从未调用 non-local static 对象的“仿真函数”，就绝不会引发构造和析构成本；真正的 non-local static 对象可没这么便宜！</p>
<p>以此技术施行于tfs和tempDir身上，结果如下：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">FileSystem</span>&#123; ... &#125;<span class="comment">// 同前</span></span><br><span class="line"><span class="function">FileSystem&amp; <span class="title">tfs</span><span class="params">()</span><span class="comment">// 这个仿真函数用来替换tfs对象；它在FileSystem class中可能是static的</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="type">static</span> FileSystem fs;</span><br><span class="line">    <span class="keyword">return</span> fs;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Directory</span>&#125;&#123; ... &#125;<span class="comment">// 同前</span></span><br><span class="line">Directory::<span class="built_in">Directory</span>( params )<span class="comment">// 同前，但原本的tfs引用现在改为tfs()</span></span><br><span class="line">&#123;</span><br><span class="line">	...</span><br><span class="line">    std::<span class="type">size_t</span> disks = <span class="built_in">tfs</span>().<span class="built_in">numDisks</span>();</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"><span class="function">Directory&amp; <span class="title">tempDir</span><span class="params">()</span><span class="comment">// 这个仿真函数用来替换tempDir对象；它在Directory class中可能是static的</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">	<span class="type">static</span> Directory td;</span><br><span class="line">    <span class="keyword">return</span> td;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>这么修改之后，这个系统程序的客户完全像以前一样使用它，唯一不同的是他们现在使用tfs()和tempDir()而不再是tfs和tempDir。也就是说他们使用函数返回的“指向static对象”的references，而不再使用static对象自身。这种结构下的reference-returning函数往往十分单纯：第一行定义并初始化一个local static对象，第二行返回它。这样的单纯性使它们成为绝佳的inline候选人，尤其如果它们被频繁调用的话（见条款30）.但是从另一个角度看，这些<strong>函数“内含static对象”的事实使它们在多线程系统中带有不确定性</strong>。<strong>再说一次，任何一种non-const static对象，不论它是local还是non-local，在多线程环境下“等待某事发生”都会有麻烦。处理这个麻烦的一种做法是：在程序的单线程启动阶段调用所有的reference-returning函数，这可消除与初始化有关的“竞速形势（race conditions）”</strong>。</p>
<p>为避免在对象初始化之前过早地使用它们，你需要做三件事：</p>
<ol>
<li>手工初始化内置型non-member对象；</li>
<li>使用成员初值列对付对象的所有成分；</li>
<li>在“初始化次序不确定性”（这对不同编译单元所定义的non-local static对象是一种折磨）氛围下加强你的设计。</li>
</ol>
<blockquote>
<ol>
<li>为内置对象进行手工初始化，因为C++不保证初始化它们。</li>
<li>构造函数最好使用成员初值列，而不要在构造函数本体内使用赋值操作。初值列列出的成员变量，其排列次序应该和它们在class中的声明次序相同。</li>
<li>为免除“跨编译单元之初始化次序”问题，请以local static 对象替换 non-local static 对象。</li>
</ol>
</blockquote>
<hr>

        
            <div class="donate-container">
    <div class="donate-button">
        <button id="donate-button">赞赏</button>
    </div>
    <div class="donate-img-container hide" id="donate-img-container">
        <img id="donate-img" src="" data-src="/myblog/img/donate.jpg">
        <p> 感谢鼓励 </p>
    </div>
</div>
        
        <br />
        <div id="comment-container">
        </div>
        <div id="disqus_thread"></div>
        <div id="lv-container"></div>
        <div class="giscus"></div>
    </div>
</div>

    </div>
</div>


<footer class="footer">
    <ul class="list-inline text-center">
        
        

        

        

        
        <li>
            <a target="_blank"  href="https://github.com/samuel-24276">
                            <span class="fa-stack fa-lg">
                                <i class="iconfont icon-github"></i>
                            </span>
            </a>
        </li>
        

        

    </ul>
    
    <p>
        <span>/</span>
        
        <span><a target="_blank" rel="noopener" href="https://github.com/samuel-24276">samuel-24276&#39;s Page</a></span>
        <span>/</span>
        
        <span><a target="_blank" rel="noopener" href="https://beian.miit.gov.cn/">北京-未备案</a></span>
        <span>/</span>
        
        <span><a href="#">返回顶部</a></span>
        <span>/</span>
        
    </p>
    
    <p>
        <span id="busuanzi_container_site_pv">
            <span id="busuanzi_value_site_pv"></span>PV
        </span>
        <span id="busuanzi_container_site_uv">
            <span id="busuanzi_value_site_uv"></span>UV
        </span>
        Created By <a target="_blank" rel="noopener" href="https://hexo.io/">Hexo</a>  Theme <a target="_blank" rel="noopener" href="https://github.com/aircloud/hexo-theme-aircloud">AirCloud</a></p>
</footer>




</body>

<script>
    // We expose some of the variables needed by the front end
    window.hexo_search_path = "search.json"
    window.hexo_root = "/myblog/"
    window.isPost = true
</script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>

<script src="/myblog/js/index.js"></script>

<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>






    <script type="text/javascript">
       (function() {
           if (typeof LivereTower === 'function') { return; }

           var j, d = document.getElementById('lv-container');

           d.setAttribute('data-id','city');
           d.setAttribute('data-uid' , 'MTAyMC80OTA5OC8yNTU5Mw==');

           j = document.createElement('script');
           j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
           j.async = true;

           d.appendChild(j);
       })();
    </script>
    <noscript>为正常使用来必力评论功能请激活JavaScript</noscript>
    </div>

</html>
